<?php
declare (strict_types=1);

namespace app\exam\logic;

use app\admin\logic\system\Config as SystemConfig;
use app\common\exception\LogicException;
use app\course\logic\payment\PaymentOrder;
use app\course\logic\payment\PaymentOrderItem;
use app\exam\logic\admin\ModuleExamination;
use app\exam\logic\admin\ModuleRollup;
use app\exam\logic\admin\ModuleRollupPaper;
use app\exam\model\{Paper as PaperModel, PaperCategory, RollupPaper};
use app\exam\model\PaperConfig;
use app\exam\model\PaperStudent;
use app\user\logic\user\FaceVerify;
use ArrayObject;
use Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Cache;
use think\facade\Request;
use think\Model;

/**
 * 试卷逻辑
 * @method join(string $string, string $string1)
 * @method whereId($paper_id)
 */
class Paper extends PaperModel
{

    /**
     * 试卷分类
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-05
     *
     * @param  [type] $param [description]
     */
    public function getPaperCategory($param)
    {
        // 数据验证
        $this->getValidate()->scene('paper_category')->failException()->check($param);
        // 参数
        $module_id = $param['module_id'];
        $cate      = $this->alias('paper')
            ->join('exam_module_examination emn', "emn.paper_id=paper.id AND emn.module_id=$module_id")
            ->column('cate');

        // 树
        return app(PaperCategory::class)->alias('cate')->join('exam_module mue', "mue.id=$module_id AND mue.mhm_id=cate.mhm_id")->where('cate.id', 'IN', $cate)->field('cate.id,cate.title,cate.num')->select();
    }

    /**
     * 获取考试基础信息
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-21
     */
    public function getPaperBase(array $param): array
    {
        // 验证数据
        $this->getValidate()->scene('paper_base')->failException()->check($param);
        // 参数
        $paper_id = intval($param['paper_id']);

        return $this->getPaperBaseById($paper_id);
    }

    /**
     * 获取套卷试卷基础信息
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-21
     */
    public function getRollupPaperBase(array $param): array
    {
        // 验证数据
        $this->getValidate()->scene('rollup_paper_base')->failException()->check($param);
        // 参数
        $rollup_id = intval($param['rollup_id']);
        $paper_id  = intval($param['paper_id']);
        // 检查试卷与套卷关系
        $data = app(RollupPaper::class)->wherePaperId($paper_id)->whereRollupId($rollup_id)->findOrEmpty();
        if ($data->isEmpty()) throw new LogicException('试卷信息错误！');
        //
        $data              = $this->getPaperBaseById($paper_id);
        $data['rollup_id'] = $rollup_id;

        return $data;
    }

    /**
     * 根据试卷ID获取考试基础信息
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-20
     *
     * @param int $id [description]
     */
    public function getPaperBaseById(int $id): array
    {
        // 获取试卷数据
        $data = $this->with(['parts'])->findOrFail($id)->toArray();
        // 定义所有试题ID集
        $topic_ids = '';
        $topics    = [];
        // 随机卷试题处理部分
        foreach ($data['parts'] as &$part) {
            switch ($data['paper_type']) {
                case 2:
                    // 随机卷获取试题
                    $part['value'] = $this->parsePartsValue2($part['value']);
                    break;

                default:
                    // 固定卷大题（材料和完型）试题处理顺序和增删题(绑定题后，大题里面增加和删除小题)
                    // foreach ($part['value'] as &$value) {
                    //     if (isset($value['sub_topics'])) {
                    //         // 按顺序获取子题
                    //         $material = app(Material::class)->whereTopicId($value['topic_id'])->order('sort')->select();
                    //         // 子题处理
                    //         if ($material->isEmpty()) {
                    //             $value['score']      = 0;
                    //             $value['sub_topics'] = [];
                    //         } else {
                    //             $sub = [];
                    //             foreach ($material as $v) {
                    //                 $score      = $part['scores']['question_type_' . $v->question_type];
                    //                 $score_miss = in_array($v->question_type, [3, 4]) ? $part['scores']['question_type_' . $v->question_type . '_miss'] : 0;
                    //                 $sub[]      = [
                    //                     'topic_id'   => $v->id,
                    //                     'score'      => $score,$value['sub_topics']
                    //                     'score_miss' => round($score_miss / 100 * $score, 1),
                    //                 ];
                    //             }
                    //             $value['sub_topics'] = array_values($sub);
                    //         }
                    //     }
                    //     unset($value);
                    // }
                    break;
            }
            $topic_ids .= implode(',', array_column($part['value'], 'topic_id')) . ',';
            $topics    = array_merge($topics, $part['value']);

            if ($data['paper_type'] == 1 && $data['question_rand']) {
                $value = $part['value'];
                shuffle($value);
                $part['value'] = $value;
            }
            unset($part['scores']);
        }

        $topic_ids = explode(',', trim($topic_ids, ','));
        // 试题集排序
        sort($topic_ids);
        $unique_code         = md5($id . '-' . json_encode($topic_ids));
        $data['unique_code'] = $unique_code;
        // redis缓存
        $redis = Cache::store('redis')->handler();
        // 缓存数据
        $redis->hSet('paper_parts_unique', $unique_code, serialize($data['parts']));
        $redis->hSet('paper_topics', (string)$id, serialize($topics));
        // 人脸验证数据
        /** @var FaceVerify $faceVerify */
        $faceVerify        = app(FaceVerify::class);
        $data['face_data'] = $faceVerify->getFaceData('exam', 2, $id, request()->mid);
        // 切屏配置
        $paperConf          = SystemConfig::get('exam.paper.config', ['number' => 0], $data['mhm_id']);
        $data['screen_cut'] = $paperConf['number'];
        // 公开考试次数
        $data['examination_answer_count'] = app(PaperAnswer::class)->getUserAnswerNumber($id, request()->mid, [
            'course_id' => 0,
            'rollup_id' => 0
        ]);

        return $data;
    }

    /**
     * 固定卷试题信息
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-03-15
     *
     * @param  [type] $value  [description]
     * @param  [type] $scores [description]
     */
    public function parsePartsValue1($topics, $scores): array
    {
        foreach ($topics as &$topic) {
            if (isset($topic['sub_topics'])) {
                foreach ($topic['sub_topics'] as &$sub) {
                    $sub = $this->_bindTopicScoreMiss($sub, $scores);
                }
            } else {
                $topic = $this->_bindTopicScoreMiss($topic, $scores);
            }
        }

        return array_values($topics);
    }

    /**
     * 绑定漏选分数
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-05-12
     *
     * @param $topic
     * @param $scores
     * @return mixed [type]         [description]
     */
    private function _bindTopicScoreMiss($topic, $scores)
    {
        $question_type = app(Topic::class)->whereId($topic['topic_id'])->value('question_type');

        // 多选、不定项
        $score_miss = 0;
        if (in_array($question_type, [3, 4])) {
            $score_miss = $scores['question_type_' . $question_type . '_miss'];
            $score_miss = round($topic['score'] * $score_miss / 100, 1);
        }
        $topic['score_miss'] = $score_miss;

        return $topic;
    }

    /**
     * 随机卷试题信息
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-03-15
     *
     * @param  [type] $value  [description]
     * @param  [type] $scores [description]
     */
    public function parsePartsValue2($parts): array
    {
        $topics = [];
        foreach ($parts as $part) {
            foreach ($part['topics'] as $key => $num) {
                if ($num) {
                    $type = explode('question_type_', $key)[1] * 1;
                    $ids  = app(Topic::class)->whereQuestionBankId($part['bank'])->whereQuestionType($type)->orderRaw('rand()')->limit($num)->column('id');
                    foreach ($ids as $id) {
                        // 多选、不定项
                        $score_miss = 0;
                        if (in_array($type, [3, 4])) {
                            $score_miss = $part['scores']['question_type_' . $type . '_miss'];
                            $score_miss = round($part['scores'][$key] * $score_miss / 100, 1);
                        }
                        // 计算分值
                        if (in_array($type, [6, 7])) {    // 材料题、完形填空
                            $material = app(Material::class)->whereTopicId($id)->order('sort')->select();
                            if ($material->isEmpty()) {
                                $topics[] = [
                                    'score'      => 0,
                                    'topic_id'   => $id,
                                    'sub_topics' => []
                                ];
                            } else {
                                $sub_topics = [];
                                foreach ($material as $v) {
                                    $sub_topics[] = [
                                        'score'      => $part['scores']['question_type_' . $v['question_type']],
                                        'score_miss' => $score_miss,
                                        'topic_id'   => $v->id
                                    ];
                                }
                                $topics[] = [
                                    'score'      => array_sum(array_column($sub_topics, 'score')),
                                    'topic_id'   => $id,
                                    'sub_topics' => $sub_topics
                                ];
                            }
                        } else {
                            $topics[] = [
                                'score'      => $part['scores'][$key],
                                'score_miss' => $score_miss,
                                'topic_id'   => $id
                            ];
                        }
                    }
                }
            }
        }

        // 随机打乱数组
        shuffle($topics);

        return array_values($topics);
    }

    /**
     * 获取试题信息及选项
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-02-05
     *
     * @param  [type] $param [description]
     */
    public function getPaperTopic($param): array
    {
        // 数据验证
        $this->getValidate()->scene('paper_topic')->failException()->check($param);
        // 参数
        $paper_id = $param['paper_id'];
        $topic_id = $param['topic_id'];
        //
        $options_rand = $this->whereId($paper_id)->value('answer_options_rand');
        //
        $topics = app(Topic::class)->getTopicInfoById($topic_id, false);

        if ($options_rand) {
            foreach ($topics as $topic) {
                if (!in_array($topic->question_type, [5, 8])) {
                    switch ($topic->topic_level) {
                        case 2:
                            foreach ($topic->topics as $item) {
                                $item->options && $item->options = $this->_shuffleOptions($item->options);
                            }
                            break;

                        default:
                            $topic->options && $topic->options = $this->_shuffleOptions($topic->options);
                            break;
                    }
                }
            }
        }

        $score = $this->_getTopicScore($topic_id, $paper_id);

        array_walk($topics, function ($val) use ($score) {
            $val->score = $score;
        });

        return $topics;
    }

    /**
     * 获取试卷试题分数
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-05-12
     *
     * @param  [type] $topic_id [description]
     * @param  [type] $paper_id [description]
     */
    private function _getTopicScore($topic_id, $paper_id)
    {
        // redis缓存
        $redis = Cache::store('redis')->handler();
        // 缓存数据
        $paper_topics = $redis->hGet('paper_topics', $paper_id);

        if ($paper_topics && $paper_topics = unserialize($paper_topics)) {
            return $paper_topics[array_search($topic_id, array_column($paper_topics, 'topic_id'))]['score'];
        } else {
            $this->getPaperBaseById($paper_id * 1);
            return $this->_getTopicScore($topic_id, $paper_id);
        }
    }

    /**
     * 打乱选项
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-29
     *
     * @param  [type] $options [description]
     * @return array [type]          [description]
     */
    private function _shuffleOptions($options): array
    {
        $options = is_array($options) ? $options : $options->toArray();
        $keys    = array_column($options, 'key');
        shuffle($options);

        return array_map(function ($opt, $key) {
            $opt['key'] = $key;
            return $opt;

        }, $options, $keys);
    }

    /**
     * 提交公开考试答案
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-03-15
     *
     * @param  [type] $param [description]
     * @return PaperAnswer|Model [type]        [description]
     * @throws Exception
     */
    public function postPaperAnswer($param)
    {
        // 数据验证
        $this->getValidate()->scene('paper_answer')->failException()->check($param);
        return $this->handlePaperAnswer($param);
    }

    /**
     * 提交套卷试卷答案
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-03-15
     *
     * @param  [type] $param [description]
     * @return PaperAnswer|Model [type]        [description]
     * @throws Exception
     */
    public function postRollupPaperAnswer($param)
    {
        // 数据验证
        $this->getValidate()->scene('rollup_paper_answer')->failException()->check($param);
        return $this->handlePaperAnswer($param);
    }

    /**
     * 提交答案
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-03-15
     *
     * @param  [type] $param [description]
     * @return PaperAnswer|Model [type]        [description]
     * @throws Exception
     */
    public function handlePaperAnswer($param)
    {
        // 参数
        $rollup_id   = intval($param['rollup_id'] ?? 0);
        $course_id   = intval($param['course_id'] ?? 0);
        $is_assign   = intval($param['is_assign'] ?? 0);
        $paper_id    = intval($param['paper_id']);
        $time_takes  = intval($param['time_takes']);
        $unique_code = $param['unique_code'];
        $answer_data = is_array($param['answer_data']) ? $param['answer_data'] : ($param['answer_data'] ? json_decode($param['answer_data'], true) : []);

        // 记录检测
        $record = app(PaperAnswer::class)->whereUserId(request()->mid)->wherePaperId($paper_id)->whereCourseId($course_id)->order('answer_times DESC')->findOrEmpty();
        // 作答次数
        $answer_times = $record->isEmpty() ? 1 : $record->answer_times + 1;
        // 作答超出限制
        $exam_number = $this->whereId($paper_id)->value('exam_number');
        if (!$rollup_id && $exam_number && $answer_times > $exam_number) throw new LogicException('超出作答次数');
        // redis缓存
        $redis = Cache::store('redis')->handler();
        // 试卷部分缓存数据
        $paper_parts = $redis->hGet('paper_parts_unique', $unique_code);
        // 重载试卷部分信息
        if (!$paper_parts) {
            // $paper_base  = $this->getPaperBaseById($paper_id);
            throw new LogicException('网络错误，请重试！');
        }
        // 解析试卷试题数据
        $paper_parts = unserialize($paper_parts);
        // 试卷部分试题信息组装
        $post = new PaperAnswerPost($unique_code, $paper_parts, $answer_data);
        $post->handle();
        // 试卷部分信息
        $paper_parts = $post->getParts();
        // 试卷试题数
        $topic_count = $post->getTopicNum();
        // 试卷部分答案
        $answer = $post->getAnswer();
        // 作答试题数
        $answer_count = $post->getAnswerNum();
        // 答对试题数
        $right_count = $post->getRightNum();
        // 试卷总分
        $score = $post->getScore();
        // 客观题得分
        $user_score = $post->getUserScore();
        // 试卷存在主观题
        $subjective = $post->getSubjective();
        // return $subj;
        // 创建记录信息数据
        $create_data = [
            'user_id'       => request()->mid,    // 考生ID
            'rollup_id'     => $rollup_id,        // 套卷ID
            'course_id'     => $course_id,        // 课程ID
            'paper_id'      => $paper_id,        // 试卷ID
            'unique_code'   => $unique_code,    // 唯一标识
            'paper_parts'   => $paper_parts,    // 试卷试题基础
            'topic_count'   => $topic_count,    // 试卷试题数
            'subjective'    => $subjective,        // 试卷有主观题
            'answer_times'  => $answer_times,    // 作答次数
            'answer_data'   => $answer_data,    // 答案数据
            'time_takes'    => $time_takes,        // 考试用时
            'commit_time'   => Request::time(),    // 交卷时间
            'paper_score'   => $score,            // 试卷总分
            'answer_count'  => $answer_count,    // 作答试题数
            'right_count'   => $right_count,    // 答对试题数
            'user_score'    => $user_score,        // 客观题得分
            'answer_status' => $rollup_id ? 2 : ($subjective ? 1 : 2),    // 阅卷状态
            'is_assign'     => $is_assign,        // 是否为指派考试
        ];

        try {
            // 数据存储
            $epar_res = PaperAnswer::create($create_data);
            if ($epar_res->isEmpty()) throw new LogicException('提交答案失败，请重试！');
            // 缓存试卷部分数据
            $redis->hSet('paper_parts_unique_handled', $unique_code . '-' . $epar_res->id, serialize($paper_parts));
            if ($answer) {
                // 答案数据
                foreach ($answer as &$ans) {
                    $ans['epar_id'] = $epar_res->id;
                }
                unset($ans);
                // 保存答案数据
                $eparo_res = app(PaperAnswerOption::class)->saveAll($answer);
                if ($eparo_res->isEmpty()) throw new LogicException('提交答案失败，请重试！');
            }
            $this->commit();
            return $epar_res;
        } catch (LogicException $e) {
            $this->rollback();
            throw $e;
        }
    }

    /**
     * 获取试卷名称
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2021-04-01
     *
     * @param  [type] $id [description]
     * @return mixed [type]     [description]
     */
    public function getTitleById($id)
    {
        return $this->whereId($id)->value('title');
    }

    /**
     * 获取用户指派考试列表
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2023-07-07
     *
     * @return mixed [type]        [description]
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function getPaperStudentList()
    {
        // 参数
        $mid = request()->mid;

        //处理数据
        $answer_data  = [];
        $student_data = app(PaperStudent::class)->where('user_id', $mid)->where('exam_status', 0)->where('exam_number', '<>', 0)->field('id,paper_id,exam_number')->select()->toArray();
        if (count($student_data)) {
            // 记录检测
            foreach ($student_data as $sd) {
                $record_count = app(PaperAnswer::class)->whereUserId($mid)->wherePaperId($sd['paper_id'])->whereCourseId(0)->whereRollupId(0)->count('id') ?? 0;
                if ($record_count >= $sd['exam_number']) {
                    $save['exam_status'] = 1;
                    app(PaperStudent::class)->whereId($sd['id'])->data($save, true)->save();
                }
                $pid               = $sd['id'];
                $answer_data[$pid] = $record_count;
            }
        }
        // 指派的考试
        $student_paper = app(PaperStudent::class)->where('user_id', $mid)->where('exam_status', 0)->with(['paper'])->order('create_time', 'DESC')->paginate(static::getListRows())->each(function ($i) use ($answer_data, $mid) {
            $pid             = $i['id'];
            $record_count    = app(PaperAnswer::class)->whereUserId($mid)->wherePaperId($i['paper_id'])->whereCourseId(0)->whereRollupId(0)->count('id') ?? 0;
            $i->answer_count = $answer_data[$pid] ?? $record_count;
            // 是否能够考试
            if ($i->exam_stime && $i->exam_etime) {
                $i->exam_open = ($i->exam_stime < time() && $i->exam_etime > time()) ? 1 : 0;
            } else if (!$i->exam_stime && !$i->exam_etime) {
                $i->exam_open = 1;
            } else {
                $i->exam_open = $i->exam_stime ? ($i->exam_stime < time() ? 1 : 0) : ($i->exam_etime > time() ? 1 : 0);
            }
        });

        $empty = new ArrayObject();
        if ($student_paper->isEmpty()) return $empty;

        return $student_paper;
    }

    /**
     * 购买考试列表
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2023-07-12
     *
     * @return mixed [type]        [description]
     * @throws DbException
     */
    public function getExamOrderList()
    {
        // 参数
        $mid    = request()->mid;
        $mhm_id = request()->mhm_id;

        // 处理数据
        $answer_data  = [];
        $paper_config = [];
        $paper_data   = [];
        $module_type  = [];
        $order_no     = [];
        $order_data   = app(PaymentOrder::class)->alias('o')->Join('payment_order_item i', "i.order_no=o.order_no AND i.product_type_id IN (8,10) AND i.user_id=$mid AND i.mhm_id=$mhm_id AND o.status=20")->whereRaw("json_extract( `ext_data`, '$.exam_status' ) is NUll")->group('i.user_id,i.product_id')->order('i.create_time ASC')->column('i.order_no,i.product_id,i.product_type_id');

        if (count($order_data)) {
            // 记录检测
            foreach ($order_data as $od) {
                $module_data = new ModuleExamination;
                $config      = [];
                if ($od['product_type_id'] == 8) {
                    $module_data = app(ModuleExamination::class)->whereId($od['product_id'])->field('module_id,paper_id')->findOrEmpty();
                    // 考试配置
                    $config = app(PaperConfig::class)->where('data_key', 'examination_' . $module_data['module_id'])->where('paper_id', $module_data['paper_id'])->field('cnf_data')->findOrEmpty();
                }
                if (!$module_data->isEmpty()) {
                    $record_count = app(PaperAnswer::class)->whereUserId($mid)->wherePaperId($module_data['paper_id'])->whereCourseId(0)->whereRollupId(0)->count('id') ?? 0;
                    if (!empty($config) && $config['cnf_data']['exam_number'] > 0 && $record_count >= $config['cnf_data']['exam_number']) {
                        $save           = app(PaymentOrderItem::class)->where('order_no', $od['order_no'])->findOrEmpty();
                        $ext_data       = $save->ext_data ? get_object_vars($save->ext_data) : [];
                        $save->ext_data = array_merge($ext_data, ['exam_status' => 1]);
                        $save->save();
                    }
                    $pid                = $od['order_no'];
                    $answer_data[$pid]  = $record_count;
                    $paper_config[$pid] = $config['cnf_data'];
                    $paper_data[$pid]   = $module_data['paper_id'];
                }
                $oid               = $od['order_no'];
                $module_type[$oid] = $od['product_type_id'];
                $order_no[]        = $od['order_no'];
            }
        }
        $empty = new ArrayObject();
        // 购买的考试
        $field       = 'order_no,product_id,product_type_id';
        $order_paper = app(PaymentOrderItem::class)->whereIn('order_no', $order_no)->where('user_id', $mid)->whereIn('product_type_id', '8,10')->whereRaw("json_extract( `ext_data`, '$.exam_status' ) is NUll")->field($field)->order('create_time', 'ASC')->paginate(static::getListRows())->each(function ($i) use ($answer_data, $paper_config, $paper_data, $module_type, $mid, $empty) {
            $pid             = $i['order_no'];
            $i->answer_count = $answer_data[$pid] ?? 0;
            // 考试配置
            $i->config = $paper_config[$pid] ?? ['exam_stime' => null, 'exam_etime' => null, 'exam_number' => 0, 'face_verification' => 0];
            // 试卷信息
            $paper_id = $paper_data[$pid] ?? 0;
            if ($i['product_type_id'] == 8) {
                $i->paper = $paper_id > 0 ? $this->whereId($paper_data[$pid])->findOrEmpty() : $empty;
            } else if ($i['product_type_id'] == 10) {
                $i->paper = app(ModuleRollup::class)->whereId($i->product_id)->findOrEmpty();
            }
            // 是否能够考试
            $start_time = $i->config['exam_stime'] ?? 0;
            $end_time   = $i->config['exam_etime'] ?? 0;
            if ($start_time && $end_time) {
                $i->exam_open = ($start_time < time() && $end_time > time()) ? 1 : 0;
            } else if (!$start_time && !$end_time) {
                $i->exam_open = 1;
            } else {
                $i->exam_open = $start_time ? ($start_time < time() ? 1 : 0) : ($end_time > time() ? 1 : 0);
            }
        });

        if (!count($order_paper)) return $empty;

        return $order_paper;
    }

    /**
     * 套卷试卷详情列表
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date 2023-07-12
     *
     * @return mixed [type]        [description]
     * @throws DbException
     */
    public function getRollUpPaperList($id)
    {
        // 参数
        $mid          = request()->mid;
        $mhm_id       = request()->mhm_id;
        $empty        = new ArrayObject();
        $paper_count  = 0;
        $answer_count = 0;

        $order_exam = app(PaymentOrderItem::class)->where('user_id', $mid)->where('mhm_id', $mhm_id)->where('product_id', $id)->where('product_type_id', '10')->whereRaw("json_extract( `ext_data`, '$.exam_status' ) is NUll")->findOrEmpty();
        if ($order_exam->isEmpty()) throw new LogicException('该套卷下试卷已全部完成');

        // 获取数据
        $paper_data = app(ModuleRollupPaper::class)->where('rollup_id', $id)->select()->each(function ($i) use ($mid, &$paper_count, &$answer_count) {
            $paper_count++;
            $module_id = app(ModuleRollup::class)->whereId($i->rollup_id)->value('module_id') ?? 0;
            // 考试配置
            $config       = app(PaperConfig::class)->where('data_key', 'rollup_' . $module_id . '_' . $i->rollup_id)->where('paper_id', $i->paper_id)->field('cnf_data')->findOrEmpty();
            $record_count = app(PaperAnswer::class)->whereUserId($mid)->wherePaperId($i->paper_id)->whereCourseId(0)->whereRollupId($i->rollup_id)->count('id') ?? 0;
            if ($config['cnf_data']['exam_number'] > 0 && $record_count >= $config['cnf_data']['exam_number']) {
                $answer_count++;
                $is_finished = 1;
            }
            $i->answer_count = $record_count;
            // 考试配置
            $i->config = $config->cnf_data ?? ['exam_stime' => null, 'exam_etime' => null, 'exam_number' => 0];
            // 试卷信息
            $i->paper = $this->whereId($i->paper_id)->findOrEmpty();
            // 是否能够考试
            $start_time = $i->config['exam_stime'] ?? 0;
            $end_time   = $i->config['exam_etime'] ?? 0;
            if ($start_time && $end_time) {
                $i->exam_open = ($start_time < time() && $end_time > time()) ? 1 : 0;
            } else if (!$start_time && !$end_time) {
                $i->exam_open = 1;
            } else {
                $i->exam_open = $start_time ? ($start_time < time() ? 1 : 0) : ($end_time > time() ? 1 : 0);
            }
            // 次数是否达到
            $i->exam_rest_num = $is_finished ?? 0;
        });

        // 数据处理
        if ($answer_count == $paper_count) {
            $save           = app(PaymentOrderItem::class)->where('user_id', $mid)->where('mhm_id', $mhm_id)->where('product_id', $id)->where('product_type_id', '10')->findOrEmpty();
            $ext_data       = $save->ext_data ? get_object_vars($save->ext_data) : [];
            $save->ext_data = array_merge($ext_data, ['exam_status' => 1]);
            if ($save->save()) throw new LogicException('该套卷下试卷已全部完成');
        }

        if (!count($paper_data)) return $empty;

        return $paper_data;
    }
}
